package com.semis.common.utils;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.semis.common.constant.HttpStatus;
import com.semis.common.exception.ServiceException;
import org.geotools.data.*;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.NameImpl;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeImpl;
import org.geotools.feature.type.GeometryDescriptorImpl;
import org.geotools.feature.type.GeometryTypeImpl;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GeoToolsUtils {

    /**
     * 保存features为shp格式
     *
     * @param features 要素类
     * @param TYPE     要素类型
     * @param shpPath  shp保存路径
     * @return 是否保存成功
     */
    public static boolean saveFeaturesToShp(List<SimpleFeature> features, SimpleFeatureType TYPE, String shpPath) {
        try {
            ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
            File shpFile = new File(shpPath);
            Map<String, Serializable> params = new HashMap<>();
            params.put("url", shpFile.toURI().toURL());
            params.put("create spatial index", Boolean.TRUE);

            ShapefileDataStore newDataStore =
                    (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
            newDataStore.setCharset(StandardCharsets.UTF_8);

            newDataStore.createSchema(TYPE);

            Transaction transaction = new DefaultTransaction("create");
            String typeName = newDataStore.getTypeNames()[0];
            SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);

            if (featureSource instanceof SimpleFeatureStore) {
                SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
                SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
                featureStore.setTransaction(transaction);
                try {
                    featureStore.addFeatures(collection);
                    generateCpgFile(shpPath);
                    transaction.commit();
                } catch (Exception problem) {
                    problem.printStackTrace();
                    transaction.rollback();
                } finally {
                    transaction.close();
                }
            } else {
                System.out.println(typeName + " does not support read/write access");
            }
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * 设置字符编码 防止乱码
     * 创建cpg文件对象
     */
    private static void generateCpgFile(String localDirPath) {
        File file = new File(localDirPath + File.separator + ".cpg");
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            writer.write("GBK,GB2312,UTF-8");
            writer.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * GeoJson to Shp
     *
     * @param geojsonPath geojson 文件路径
     * @param shpPath     shp 文件路径
     * @return 转换是否成功
     */
    public static boolean transformGeoJsonToShp(String geojsonPath, String shpPath) {
        try {
            // open geojson
            InputStream in = new FileInputStream(geojsonPath);
            GeometryJSON gjson = new GeometryJSON();
            FeatureJSON fjson = new FeatureJSON(gjson);
            FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(in);
            // convert schema for shapefile
            SimpleFeatureType schema = features.getSchema();
            GeometryDescriptor geom = schema.getGeometryDescriptor();
            // geojson文件属性
            List<AttributeDescriptor> attributes = schema.getAttributeDescriptors();
            // geojson文件空间类型（必须在第一个）
            GeometryType geomType = null;
            List<AttributeDescriptor> attribs = new ArrayList<>();
            for (AttributeDescriptor attrib : attributes) {
                AttributeType type = attrib.getType();
                if (type instanceof GeometryType) {
                    geomType = (GeometryType) type;
                } else {
                    attribs.add(attrib);
                }
            }
            if (geomType == null)
                return false;

            // 使用geomType创建gt
            GeometryTypeImpl gt = new GeometryTypeImpl(new NameImpl("the_geom"), geomType.getBinding(),
                    geom.getCoordinateReferenceSystem() == null ? DefaultGeographicCRS.WGS84 : geom.getCoordinateReferenceSystem(), // 用户未指定则默认为wgs84
                    geomType.isIdentified(), geomType.isAbstract(), geomType.getRestrictions(),
                    geomType.getSuper(), geomType.getDescription());

            // 创建识别符
            GeometryDescriptor geomDesc = new GeometryDescriptorImpl(gt, new NameImpl("the_geom"), geom.getMinOccurs(),
                    geom.getMaxOccurs(), geom.isNillable(), geom.getDefaultValue());

            // the_geom 属性必须在第一个
            attribs.add(0, geomDesc);

            SimpleFeatureType outSchema = new SimpleFeatureTypeImpl(schema.getName(), attribs, geomDesc, schema.isAbstract(),
                    schema.getRestrictions(), schema.getSuper(), schema.getDescription());
            List<SimpleFeature> outFeatures = new ArrayList<>();
            try (FeatureIterator<SimpleFeature> features2 = features.features()) {
                while (features2.hasNext()) {
                    SimpleFeature f = features2.next();
                    SimpleFeature reType = DataUtilities.reType(outSchema, f, true);

                    reType.setAttribute(outSchema.getGeometryDescriptor().getName(),
                            f.getAttribute(schema.getGeometryDescriptor().getName()));

                    outFeatures.add(reType);
                }
            }
            return saveFeaturesToShp(outFeatures, outSchema, shpPath);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Shp to GeoJson
     *
     * @param shpPath     shp 文件路径
     * @param geojsonPath geojson 文件路径
     * @return 转换是否成功
     */
    public static boolean transformShpToGeoJson(String shpPath, String geojsonPath) {
        try {
            File file = new File(shpPath);
            FileDataStore myData = FileDataStoreFinder.getDataStore(file);
            // 设置解码方式
            ((ShapefileDataStore) myData).setCharset(StandardCharsets.UTF_8);
            SimpleFeatureSource source = myData.getFeatureSource();
            SimpleFeatureType schema = source.getSchema();
            Query query = new Query(schema.getTypeName());

            FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures(query);
            FeatureJSON fjson = new FeatureJSON();
            File geojson = new File(geojsonPath);
            try (FeatureIterator<SimpleFeature> featureIterator = collection.features();
                 StringWriter writer = new StringWriter();
                 BufferedWriter buffer = new BufferedWriter(Files.newBufferedWriter(geojson.toPath(), StandardCharsets.UTF_8))) {
                writer.write("{\"type\":\"FeatureCollection\",\"crs\":");
                fjson.writeCRS(schema.getCoordinateReferenceSystem(), writer);
                writer.write(",");
                writer.write("\"features\":");
                writer.write("[");
                while (featureIterator.hasNext()) {
                    SimpleFeature feature = featureIterator.next();
                    fjson.writeFeature(feature, writer);
                    if (featureIterator.hasNext())
                        writer.write(",");
                }
                writer.write("]");
                writer.write("}");
                buffer.write(writer.toString());
                return true;
            } catch (IOException e) {
                return false;
            }
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * 将shp的zip文件转为geoJson字符串
     *
     * @param zipFile zip文件的本地路径
     * @return
     */
    public static String transformShpZipToGeoJson(String zipFile) {
        if (!FileUtil.exist(zipFile)) {
            throw new ServiceException("文件存在问题！", HttpStatus.WARN);
        }

        String unZipPath = StringUtils.removeEnd(zipFile, ".zip");

        // 解压地块系统数据地理信息shp文件ZIP压缩包
        File fileFolder = ZipUtil.unzip(zipFile, unZipPath, Charset.forName("GBK"));
        // 使用Hutool，读取目录下所有文件（包含子文件夹），并过滤出.shp文件
        List<File> shpList = FileUtil.loopFiles(fileFolder, filename -> {
            if (filename.getName().endsWith(".shp")) {
                return true;
            }
            return false;
        });

        if (shpList.size() == 0) {
            throw new ServiceException("未找到shp文件！", HttpStatus.WARN);
        }
        // 坐标系转化
        File files = geographicCoordinateConversion(shpList.get(0));
        String geoJson = transformShpToGeoJson(files);

        FileUtil.del(unZipPath);
        return geoJson;
    }

    /**
     * Shp文件 to geoJson字符串
     *
     * @param shpFile Shp文件
     */
    public static String transformShpToGeoJson(File shpFile) {
        FeatureJSON fjson = new FeatureJSON();
        try {
            // File shpFile = new File(shpPath);
            StringBuffer sb = new StringBuffer();
            sb.append("{\"type\": \"FeatureCollection\",\"features\": ");

            ShapefileDataStore shpDataStore = null;

            shpDataStore = new ShapefileDataStore(shpFile.toURL());
            //设置编码
            Charset charset = Charset.forName("GBK");
            shpDataStore.setCharset(charset);
            String typeName = shpDataStore.getTypeNames()[0];
            SimpleFeatureSource featureSource = null;
            featureSource = shpDataStore.getFeatureSource(typeName);
            SimpleFeatureCollection result = featureSource.getFeatures();
            SimpleFeatureIterator itertor = result.features();
            JSONArray array = new JSONArray();
            while (itertor.hasNext()) {
                SimpleFeature feature = itertor.next();
                StringWriter writer = new StringWriter();
                fjson.writeFeature(feature, writer);
                JSONObject json = JSONObject.parseObject(writer.toString());
                array.add(json);
            }
            itertor.close();
            sb.append(array);
            sb.append("}");

            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 获取shp文件坐标系名称
     *
     * @param file shp文件（后缀.shp，注：dbf、shp、shx、prj四个文件不可缺）
     * @return
     */
    public static String getCoordinateSystem(File file) {
        try {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("url", file.toURI().toURL());
            DataStore dataStore = DataStoreFinder.getDataStore(map);
            ShapefileDataStore shapefileDataStore = (ShapefileDataStore) dataStore;
            SimpleFeatureType simpleFeatureType = shapefileDataStore.getSchema();
            CoordinateReferenceSystem coordinateReferenceSystem = simpleFeatureType.getCoordinateReferenceSystem();
            String coordinateSystem = coordinateReferenceSystem.getName().toString();
            return coordinateSystem;
        } catch (Exception e) {
            System.out.println("读取shp文件坐标系时出错：" + e.getMessage());
            e.printStackTrace();
            return "未读取到坐标系";
        }
    }

    /**
     * shp文件坐标系转换
     *
     * @param inputShp
     * @param outputShp
     * @param targetSrid
     * @throws Exception
     */
    public static void projectShape(String inputShp, String outputShp, Integer targetSrid) throws Exception {
        System.setProperty("org.geotools.referencing.forceXY", "true");
        //源shape文件
        ShapefileDataStore shapeDS = (ShapefileDataStore) new ShapefileDataStoreFactory().createDataStore(new File(inputShp).toURI().toURL());
        //创建目标shape文件对象
        Map<String, Serializable> params = new HashMap<String, Serializable>();
        FileDataStoreFactorySpi factory = new ShapefileDataStoreFactory();
        params.put(ShapefileDataStoreFactory.URLP.key, new File(outputShp).toURI().toURL());
        ShapefileDataStore ds = (ShapefileDataStore) factory.createNewDataStore(params);
        CoordinateReferenceSystem crs = CRS.decode("EPSG:" + targetSrid);
        CoordinateReferenceSystem crsOld = shapeDS.getSchema().getCoordinateReferenceSystem();

        System.out.println(crsOld.getName());
        // 设置属性
        SimpleFeatureSource fs = shapeDS.getFeatureSource(shapeDS.getTypeNames()[0]);
        //下面这行还有其他写法，根据源shape文件的simpleFeatureType可以不用retype，而直接用fs.getSchema设置
        System.out.println(crs.getName());

        ds.createSchema(SimpleFeatureTypeBuilder.retype(fs.getSchema(), crs));
        ds.forceSchemaCRS(crs);

        //设置writer
        FeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriter(ds.getTypeNames()[0], Transaction.AUTO_COMMIT);

        //写记录
        SimpleFeatureIterator it = fs.getFeatures().features();
        MathTransform transform = CRS.findMathTransform(crsOld, crs, true);
        //遍历要素
        try {
            while (it.hasNext()) {
                SimpleFeature f = it.next();
                SimpleFeature fNew = writer.next();
                fNew.setAttributes(f.getAttributes());

                //图形坐标变换
                Geometry geom = JTS.transform((Geometry) f.getAttribute("the_geom"), transform);
                fNew.setAttribute("the_geom", geom);

            }
        } finally {
            it.close();
        }
        writer.write();
        writer.close();
        ds.dispose();
        shapeDS.dispose();
    }

    /**
     * shp文件坐标系转为EPSG:4326
     * EPSG:4326 坐标系（地理坐标）
     * EPSG:3857 坐标系（投影坐标）
     * CGCS2000 国家大地坐标系
     *
     * @param file shp文件
     * @return shp文件
     */
    public static File geographicCoordinateConversion(File file) {
        if (!"EPSG:WGS 84".equals(GeoToolsUtils.getCoordinateSystem(file))) {
            try {
                // 新文件
                File newFile = new File(file.getParent() + File.separator + "out.shp");
                GeoToolsUtils.projectShape(file.getPath(), newFile.getPath(), 4326);
                return newFile;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            return file;
        }
    }

    // 测试
    public static void main(String[] args) throws Exception {
        // File file = new File("D:\\Download\\个人办公\\励图高科\\青岛土壤\\shp文件\\测试数据-土壤转运路线\\测试数据-土壤转运路线.shp");
        // System.out.println(getCoordinateSystem(file));
        // projectShape("D:\\Download\\个人办公\\励图高科\\青岛土壤\\shp文件\\中车四方智汇港项目105地块_初调地块边界\\详细调查3702032330085.shp",
        //         "D:\\Download\\个人办公\\励图高科\\青岛土壤\\shp文件\\中车四方智汇港项目105地块_初调地块边界\\详细调查001.shp",
        //         4326);
    }
}
